// Copyright 2017 Emilie Gillet.
//
// Author: Emilie Gillet (emilie.o.gillet@gmail.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to enable, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// 
// See http://creativecommons.org/licenses/MIT/ for more information.
//
// -----------------------------------------------------------------------------
//
// Convert the phase counters generated by RampGenerator into actual variable
// slope waveforms (or variants, like EOA/EOR pulses).

#ifndef TIDES_RAMP_SHAPER_H_
#define TIDES_RAMP_SHAPER_H_

#include "stmlib/dsp/dsp.h"
#include "stmlib/dsp/parameter_interpolator.h"
#include "stmlib/dsp/polyblep.h"

#include "tides2/ramp_generator.h"

namespace tides {

class RampShaper {
 public:
  RampShaper() { }
  ~RampShaper() { }
  
  void Init() {
    next_sample_ = 0.0f;
    previous_phase_shift_ = 0.0f;
    going_up_ = true;
  }
  
  inline float BandLimitedPulse(float phase, float frequency, float pw) {
    CONSTRAIN(pw, frequency * 2.0f, 1.0f - 2.0f * frequency);
    float this_sample = next_sample_;
    float next_sample = 0.0f;
    
    float wrap_point = pw;
    if (phase < pw * 0.5f) {
      wrap_point = 0.0f;
    } else if (phase > 0.5f + pw * 0.5f) {
      wrap_point = 1.0f;
    }
    
    if (going_up_ ^ (phase < pw)) {
      float t = (phase - wrap_point) / frequency;
      float discontinuity = 1.0f;
      if (wrap_point != pw) {
        discontinuity = -discontinuity;
      }
      if (frequency < 0.0f) {
        discontinuity = -discontinuity;
      }
      this_sample += stmlib::ThisBlepSample(t) * discontinuity;
      next_sample += stmlib::NextBlepSample(t) * discontinuity;
      going_up_ = phase < pw;
    }
    next_sample += phase < pw ? 0.0f : 1.0f;
    next_sample_ = next_sample;
    return this_sample;
  }
  
  template<RampMode ramp_mode, Range range>
  inline float Slope(
      float phase, float phase_shift, float frequency, float pw) {
    if (ramp_mode == RAMP_MODE_AD) {
      return SkewedRamp(phase, 0.0f, frequency, pw);
    } else if (ramp_mode == RAMP_MODE_AR) {
      return phase;
    } else {
      if (range == RANGE_CONTROL) {
        return SkewedRamp(phase, phase_shift, frequency, pw);
      } else {
        return BandLimitedSlope(phase, phase_shift, frequency, pw);
      }
    }
  }
  
  template<RampMode ramp_mode>
  inline float EOA(float phase, float frequency, float pw) {
    if (ramp_mode == RAMP_MODE_LOOPING) {
      return BandLimitedPulse(phase, frequency, pw);
    } else if (ramp_mode == RAMP_MODE_AR) {
      return phase >= 0.5f ? 1.0f : 0.0f;
    } else {
      return phase >= pw ? 1.0f : 0.0f;
    }
  }

  template<RampMode ramp_mode>
  inline float EOR(float phase, float frequency, float pw) {
    if (ramp_mode == RAMP_MODE_LOOPING) {
      return 1.0f - BandLimitedPulse(
          phase, frequency, std::min(0.5f, 96.0f * frequency));
    } else {
      return phase >= 1.0f;
    }
  }
  
 private:
  inline float BandLimitedSlope(
      float phase, float phase_shift, float frequency, float pw) {
    if (phase_shift) {
      phase += phase_shift;
      frequency += phase_shift - previous_phase_shift_;
      previous_phase_shift_ = phase_shift;

      if (phase >= 1.0f) {
        phase -= 1.0f;
      } else if (phase < 0.0f) {
        phase += 1.0f;
      }
    }
    
    CONSTRAIN(pw, fabsf(frequency) * 2.0f, 1.0f - 2.0f * fabsf(frequency));
    float this_sample = next_sample_;
    float next_sample = 0.0f;
    
    float wrap_point = pw;
    if (phase < pw * 0.5f) {
      wrap_point = 0.0f;
    } else if (phase > 0.5f + pw * 0.5f) {
      wrap_point = 1.0f;
    }
    
    const float slope_up = 1.0f / pw;
    const float slope_down = 1.0f / (1.0f - pw);
    if (going_up_ ^ (phase < pw)) {
      float t = (phase - wrap_point) / frequency;
      float discontinuity = -(slope_up + slope_down) * frequency;
      if (wrap_point != pw) {
        discontinuity = -discontinuity;
      }
      if (frequency < 0.0f) {
        discontinuity = -discontinuity;
      }
      this_sample += stmlib::ThisIntegratedBlepSample(t) * discontinuity;
      next_sample += stmlib::NextIntegratedBlepSample(t) * discontinuity;
      going_up_ = phase < pw;
    }
    next_sample += going_up_
      ? phase * slope_up
      : 1.0f - (phase - pw) * slope_down;
    next_sample_ = next_sample;
    return this_sample;
  }
  
  inline float SkewedRamp(
      float phase, float phase_shift, float frequency, float pw) {
    if (phase_shift) {
      phase += phase_shift;
      frequency += phase_shift - previous_phase_shift_;
      previous_phase_shift_ = phase_shift;

      if (phase >= 1.0f) {
        phase -= 1.0f;
      } else if (phase < 0.0f) {
        phase += 1.0f;
      }
    }
    
    CONSTRAIN(pw, fabsf(frequency) * 2.0f, 1.0f - 2.0f * fabsf(frequency));
    const float slope_up = 0.5f / pw;
    const float slope_down = 0.5f / (1.0f - pw);
    return phase < pw ? phase * slope_up : (phase - pw) * slope_down + 0.5f;
  }

  float next_sample_;
  float previous_phase_shift_;
  bool going_up_;

  DISALLOW_COPY_AND_ASSIGN(RampShaper);
};

class RampWaveshaper {
 public:
  RampWaveshaper() { }
  ~RampWaveshaper() { }
 
  void Init() {
    previous_input_ = 0.0f;
    previous_output_ = 0.0f;
    breakpoint_ = 0.0f;
  }
  
  template<RampMode ramp_mode>
  inline float Shape(
      float input,
      const int16_t* shape,
      float shape_fractional) {
    float ws_index = 1024.0f * input;
    MAKE_INTEGRAL_FRACTIONAL(ws_index)
    ws_index_integral &= 1023;
    float x0 = static_cast<float>(shape[ws_index_integral]) / 32768.0f;
    float x1 = static_cast<float>(shape[ws_index_integral + 1]) / 32768.0f;
    float y0 = static_cast<float>(shape[ws_index_integral + 1025]) / 32768.0f;
    float y1 = static_cast<float>(shape[ws_index_integral + 1026]) / 32768.0f;
    float x = x0 + (x1 - x0) * ws_index_fractional;
    float y = y0 + (y1 - y0) * ws_index_fractional;
    float output = x + (y - x) * shape_fractional;
    
    if (ramp_mode != RAMP_MODE_AR) {
      return output;
    } else {
      if (previous_input_ <= 0.5f && input > 0.5f) {
        breakpoint_ = previous_output_;
      } else if (previous_input_ > 0.5f && input < 0.5f) {
        breakpoint_ = previous_output_;
      } else if (input == 1.0f) {
        breakpoint_ = 1.0f;
      } else if (input == 0.5f) {
        breakpoint_ = 0.0f;
      }
      if (input <= 0.5f) {
        output = breakpoint_ + (1.0f - breakpoint_) * output;
      } else {
        output = breakpoint_ * output;
      }
      previous_input_ = input;
      previous_output_ = output;
      return output;
    }
  }
  
 private:
  float previous_input_;
  float previous_output_;
  float breakpoint_;
   
  DISALLOW_COPY_AND_ASSIGN(RampWaveshaper);
};

}  // namespace tides

#endif  // TIDES_RAMP_SHAPER_H_
